local function init()
  local obj = {
    root = {},
    options = { noreduce = {} },
  }

  obj._stack = { obj.root }
  return obj
end

-- Generates a lua table from an XML content string.
-- It is a simplified handler which attempts
-- to generate a more 'natural' table based structure which
-- supports many common XML formats.
--
-- The XML tree structure is mapped directly into a recursive
-- table structure with node names as keys and child elements
-- as either a table of values or directly as a string value
-- for text. Where there is only a single child element this
-- is inserted as a named key - if there are multiple
-- elements these are inserted as a vector (in some cases it
-- may be preferable to always insert elements as a vector
-- which can be specified on a per element basis in the
-- options).  Attributes are inserted as a child element with
-- a key of '_attr'.
--
-- Only Tag/Text & CDATA elements are processed - all others
-- are ignored.
--
-- This format has some limitations - primarily
--
-- * Mixed-Content behaves unpredictably - the relationship
--   between text elements and embedded tags is lost and
--   multiple levels of mixed content does not work
-- * If a leaf element has both a text element and attributes
--   then the text must be accessed through a vector (to
--   provide a container for the attribute)
--
-- In general however this format is relatively useful.
--
-- It is much easier to understand by running some test
-- data through 'testxml.lua -simpletree' than to read this)
--
-- Options
-- =======
--    options.noreduce = { <tag> = bool,.. }
--        - Nodes not to reduce children vector even if only
--          one child
--
--  License:
--  ========
--
--  This code is freely distributable under the terms of the [MIT license](LICENSE).
--
--@author Paul Chakravarti (paulc@passtheaardvark.com)
--@author Manoel Campos da Silva Filho
local tree = init()

---Instantiates a new handler object.
--Each instance can handle a single XML.
--By using such a constructor, you can parse
--multiple XML files in the same application.
--@return the handler instance
function tree:new()
  local obj = init()

  obj.__index = self
  setmetatable(obj, self)

  return obj
end

--- Recursively removes redundant vectors for nodes
-- with single child elements
function tree:reduce(node, key, parent)
  for k, v in pairs(node) do
    if type(v) == "table" then
      self:reduce(v, k, node)
    end
  end
  if #node == 1 and not self.options.noreduce[key] and node._attr == nil then
    parent[key] = node[1]
  end
end

--- If an object is not an array,
-- creates an empty array and insert that object as the 1st element.
--
-- It's a workaround for duplicated XML tags outside an inner tag. Check issue #55 for details.
-- It checks if a given tag already exists on the parsing stack.
-- In such a case, if that tag is represented as a single element,
-- an array is created and that element is inserted on it.
-- The existing tag is then replaced by the created array.
-- For instance, if we have a tag x = {attr1=1, attr2=2}
-- and another x tag is found, the previous entry will be changed to an array
-- x = {{attr1=1, attr2=2}}. This way, the duplicated tag will be
-- inserted into this array as x = {{attr1=1, attr2=2}, {attr1=3, attr2=4}}
-- https://github.com/manoelcampos/xml2lua/issues/55
--
-- @param obj the object to try to convert to an array
-- @return the same object if it's already an array or a new array with the object
--         as the 1st element.
local function convertObjectToArray(obj)
  --#obj == 0 verifies if the field is not an array
  if #obj == 0 then
    local array = {}
    table.insert(array, obj)
    return array
  end

  return obj
end

---Parses a start tag.
-- @param tag a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function tree:starttag(tag)
  local node = {}
  if self.parseAttributes == true then
    node._attr = tag.attrs
  end

  --Table in the stack representing the tag being processed
  local current = self._stack[#self._stack]

  if current[tag.name] then
    local array = convertObjectToArray(current[tag.name])
    table.insert(array, node)
    current[tag.name] = array
  else
    current[tag.name] = { node }
  end

  table.insert(self._stack, node)
end

---Parses an end tag.
-- @param tag a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function tree:endtag(tag, s)
  --Table in the stack representing the tag being processed
  --Table in the stack representing the containing tag of the current tag
  local prev = self._stack[#self._stack - 1]
  if not prev[tag.name] then
    error("XML Error - Unmatched Tag [" .. s .. ":" .. tag.name .. "]\n")
  end
  if prev == self.root then
    -- Once parsing complete, recursively reduce tree
    self:reduce(prev, nil, nil)
  end

  table.remove(self._stack)
end

---Parses a tag content.
-- @param t text to process
function tree:text(text)
  local current = self._stack[#self._stack]
  table.insert(current, text)
end

---Parses CDATA tag content.
tree.cdata = tree.text
tree.__index = tree
return function()
  return tree:new()
end
